{-------------------------------------------------------------------------------
Title: SIPS Legato Script
Author: R.D.Villwock aka 'Big Bob'
First Written: Feb 20, 2006
Current Version: 1.51
Last Modified: June 5, 2007 

  This script is part of a 'Suite' of scripts for performance effects known as

the 'Solo Instrument Performance Suite', SIPS.  Currently, there are two members

in the suite, the SIPS Legato Script and the SIPS Vibrato Script, SLS and SVS.

This version of the SLS also contains the Solo-Mode Logic for the suite and is

thus coded to be run as the first member of the suite. V150 also adds the code
needed to allow the SLS to be chained with the latest version of Nils Liberg's
VXF Script.

*******************************************************************************

NOTE: To compile this source code, you will need to use V1.22.4 or higher of
Nils Liberg's KScript Editor. Since this modularized version of SIPS uses the
'import' feature of the NL Editor, you will also need the source text files
for all the 'External Modules' (in the same folder) to compile this script.
*******************************************************************************
External SIPS Modules Needed:
  SIPSXM-GSR     General Support Routines
  SIPSXM-SLS     Common SIPS Routines and Data types, Customized for the SLS
  SIPSXM-Ren     Loads ui_control callbacks for the Rename-Panel
  SIPSXM-UPS     User Preset Support for SIPS (including Rename & Error Panels)
Other External Modules Needed:
  ISCS_V101      Interscript Communication System 
-------------------------------------------------------------------------------}

import "SIPSXM-GSR V151.txt"
import "SIPSXM-SLS V151.txt" 

import "SIPSXM-UPS V151.txt"
import "ISCS_V101.txt"


on init
  make_perfview
  SET_CONDITION(NO_SYS_SCRIPT_PEDAL)
  declare const Pedal := 64   { Use standard sustain pedal CC = 64 }
  {------------------------- ISCS Interface Block ---------------------------}
                 {------ SIPS SLS Code Assignments ------}

  declare const SLS_ID := 1001   { Registered SID for the SLS Script }

                           { Omni Messages }

  declare const starting_phrase := 1    { Starting first note after silence }

  declare const ending_phrase := 2      { Ending last note of phrase }

  declare const ending_prior_note := 3  { Ending prior fade_out note }
  declare const starting_xfade := 4     { Starting a legato transition }

  declare const ending_xfade := 5       { Ending a legato transition }
  declare const new_inst_range := 6     { Instrument Range:  EP[0], EP[1] = low, top }
                         { Client Messages }

  declare const import_block := 1       { Import Preset if Sender is SLS }  
                       { Local Call Selectors } 

  declare const FetchPreset := 1    { Trigger Recall_Preset }                       

                 {------- VXF Code Assignments -------}
  declare const VXF_ID := 1500   { Registered SID for the VXF }
                           { Omni Messages }

                      { Client Messages/Replies }

  declare const get_nlayers := 1 { Return current nlayers in EP[0] }

    declare const vxfFULL := 2   { VXF at max polyphony, discard new note }

  

  on_init_ISCS(SLS_ID)          { Generate ISCS data declarations }  

  {--------------------- End of ISCS Interface Block ------------------------}  
  
  { --------- Environment Parameters for the SLS-V105 & V110..V150 ----------}
  declare const SLS  := 1001  { SIPS Member Script Code for the SLS }
  declare const SVS  := 1002  { SIPS Member Script Code for the SVS }
  declare const V105 := 100   { User Preset Format Code for V105 & V1051 }
  declare const V110 := 101   { User Preset Format Code for V110 }
  declare const V150 := 102   { User Preset Format Code for V150 }
  declare const MSC  := SLS   { Member Script Code for this script }
  declare const PFC  := V150  { User Preset Format Code for this script }

  { ----------------- Global Inter-Script Data Array ----------------- }

  on_init_GlobalArray   { Declare and initialize Global Data Array, *GSR* } 

  { ---------------------- Preset Limits --------------------- }
  declare const KeyParms := 10     { #of paramaters for a Built-in Preset }
  declare const MainPresets := 18  { Number of Main (ie Built-In) presets }

  declare const MainSize := MainPresets * KeyParms  { Must not exceed 512 }
  on_init_Set_UserLimits           { Establish User Preset Limits, *UPS* }


                         { RlsMode Menu Indices }

  declare const RM_Knob := 1  { Use RlsFade Knob setting }                         

  declare const RM_KeyUp := 2 { Fade-out ends at KeyUp } 

                         { Offset Menu Indices }

  declare const Ofst_Auto1 := 1  { Menu Index, Time since last note started }

  declare const Ofst_Auto2 := 2  { Menu Index, Time since phrase startedx }

  declare const Ofst_Man := 3    { Menu Index, Time of Offset Knob setting }

  declare const Ofst_Rand := 4   { Menu Index, Time of Knob + Random add-on } 
                         { SLS Mode Menu Indices }

  declare const Legato_Mode := 1 { Menu Index, Legato Mode Enabled }

  declare const Solo_Mode := 2   { Menu Index, Solo Mode Enabled }
  declare const SIPS_Off := 3    { Menu Index, SLS Disabled/Bypassed }  

                              { Defaults }
  declare const DefRng := 100    { Default MIDI CC add-on range = 100% }
  declare const No_Cmd  := -1    { Preset Idle, negative values are used for commands }   

                           { Control Limits }
  declare const MaxAtkFade := 100   { Max Attack time of seg 1 = 100% of XTime }                             
  declare const MaxBend := 100      { Max bend in cents, Edit Box only }

  declare const MaxBTime := 1000    { Max Bend Time in msecs }
  declare const MaxNodeVol := 90    { Max Node volume = 90% of Vmax }

  declare const MaxOfst := 2000     { Max manual sample-start Offset in ms }
  declare const MaxRlsFade := 100   { Max release fade in percent of XTime }
  declare const MaxRng := 200       { Max CC Range of add-on adjustment = 200% }
  declare const MaxSlope := 50      { Max bend slope = 5.0 }
  declare const MaxXTime := 1000    { Max Crossfade Time in msecs (keep < 10000 }
  declare const MinAtkFade := 10    { Min attack time for seg 1 = 10% of XTime }
  declare const MinNodeVol := 10    { Min node volume = 10% of Vmax } 

  declare const MinRng := 10        { Min CC Range of add-on adjustment = 10% }
  declare const MinSlope := 10      { Min bend slope = 1.0 }
  declare const TopCC := 119        { Top useable MIDI CC# }  
                         { Miscellaneous }
  declare const ModWheel := 1       { MIDI Mod Wheel code }
  declare const Mute := -100000     { -100 db of silence }

  declare const NoID := 0           { Impossible ID }
  declare const NoParm := -100000   { Used as a token for 'undefined' parameter }
  declare const false := 0          { Boolean False }
  declare const true := 1           { Boolean True }
  declare const PitchWheel := 128   { K2 CC code for bender wheel }
  declare const Thru_Note := 12345  { Event Tag for pass-thru notes }
  declare const Tick := 100         { Staircase step time in usec }
  
  { ---------------------- String Constants ------------------------ }

  declare @SetCap            { Normal caption for SetIR button }

    SetCap := ' Set Inst. Range'

  declare @LowCap            { Caption for low key in IR }

    LowCap := ' Low Key: '

  declare @TopCap            { Caption for high key in IR }

    TopCap := ' High Key: '

  { ------------------- String Array Constants --------------------- }
  on_init_CC           { Allocate and intialize MIDI CC menu names, *GSR* }

  on_init_CharSet      { Allocate and initialize User Name Character Set, *UPS* }
  on_init_ScaleTones   { Allocate and initialize the Scale Tone array, *GSR* }
  
  { ---------------------- Preset/Panel System -------------------------- }

  declare PresetBfr[MaxParms]     { Buffer to hold one Preset, Main or User }
  on_init_Set_UIX      { Assign an index to each UI control, *SLS* }
  { ------------------------- Main Presets ------------------------ }
  declare MainParms[MainSize] := ...  
                             { Presets by BB } ...

    {Inst 0} ( 60,100,50,  60,50, 10,100, RM_Knob, 250,Ofst_man, ...
                             { Presets by TK } ...

    {Inst 1}   82, 27,60,  57,10, 44,100, RM_Knob, 129, Ofst_Rand, ...

    {Inst 2}  477, 37,66, 127,34, 43, 10, RM_Knob, 190, Ofst_Rand, ...

    {Inst 3}   89, 20,45,  66,16, 29,100, RM_Knob, 227, Ofst_Rand, ...

    {Inst 4}   69, 20,34,  23,15, 24,100, RM_Knob, 145, Ofst_Rand, ... 

    {Inst 5}  158, 53,78, 123,20, 44,100, RM_Knob,  62,  Ofst_Man, ...

    {Inst 6}   74, 22,53,  55,13, 38, 54, RM_Knob, 334,  Ofst_Man, ... 

    {Inst 7}   71, 22,46,  54,13, 23,100, RM_Knob, 290, Ofst_Rand, ...

    {Inst 8}  114, 47,68, 138,35, 52,100, RM_Knob,  82,  Ofst_Man, ... 

    {Inst 9}   66, 48,67,  78,18, 64,100, RM_Knob,  46, Ofst_Rand, ... 

    {Inst 10}  81, 27,61,  43,14, 16,100, RM_Knob,  27, Ofst_Rand, ... 

    {Inst 11} 180, 37,66,  58,23, 20,100, RM_Knob,   0,Ofst_Auto2, ...
                             { Presets by AK } ...
    {Inst 12} 105, 10,53, 151,30, 16, 22, RM_Knob,   0, Ofst_Rand, ...
    {Inst 13}  85, 26,65, 135,28, 22, 34, RM_Knob,1016, Ofst_Rand, ...
    {Inst 14} 248, 26,61,  57,28,  9, 84, RM_Knob, 864,  Ofst_Man, ...
    {Inst 15} 316, 13,90,  79,28, 13, 24, RM_Knob, 214,Ofst_Auto1, ...

    {Inst 16}  98, 12,90,  63,20, 44, 24, RM_Knob,  62,  Ofst_Man, ...

    {Inst 17}  24, 10,47,  24,18, 22,  5, RM_Knob,   0, Ofst_Rand)
  { -------------------------- User Presets -------------------------- }
  on_init_UserPresets  { Build default or restore saved User Presets, *UPS* }
  
  { ------------ Main Control Panel, Column 1 --------------- }
  declare ui_label XFadeCap1 (1,1) { XFade time caption }

    set_text(XFadeCap1,' Crossfade Time') 
  declare ui_menu Preset   { Instrument Preset Menu Button }

    add_menu_item (Preset,' -- Presets --',No_Cmd)  { Menu Hdr }
    Preset := No_Cmd   { Default Preset to Display Menu Header }    
    { ------------------- Main Presets ----------------------- }
    add_menu_item (Preset,'Clarinet 1              BB',    0)
    add_menu_item (Preset,'Bassoon               TK1',     1)  
    add_menu_item (Preset,'Cello 1                 TK2',   2)
    add_menu_item (Preset,'Cello 2                AK1',   13)
    add_menu_item (Preset,'Clarinet 2             TK3',    3)

    add_menu_item (Preset,'Flute                  TK4',    4)
    add_menu_item (Preset,'French Horn 1         TK5',     5)
    add_menu_item (Preset,'French Horn 2        AK2',     16)
    add_menu_item (Preset,'Horn Section          AK3',    15)
    add_menu_item (Preset,'Oboe                  TK6',     6)

    add_menu_item (Preset,'Picolo Flute           TK7',    7)
    add_menu_item (Preset,'Trombone             TK8',      8)

    add_menu_item (Preset,'Trumpet 1             TK9',     9)
    add_menu_item (Preset,'Trumpet 2            AK4',     17)

    add_menu_item (Preset,'Violin 1                TK10', 10)
    add_menu_item (Preset,'Violin 2                AK5',  12)

    add_menu_item (Preset,'Violin Section 1        TK11', 11)
    add_menu_item (Preset,'Violin Section 2       AK6',   14)
  { -------------------- User Presets ----------------------- }
  on_init_Add_UserMenu  { Add User Presets and Commands to Menu, *UPS* }
  { ------------------ End Preset Menu ---------------------- }
  on_init_XTime { Declare XTime Knob and associated MIDI controls, *SLS* }

  { ------------ Main Control Panel, Column 2 --------------- }
  declare ui_label XFadeCap2 (2,1)  { XFade Contouring Caption }

    set_text(XFadeCap2,'--- Crossfade Contouring Controls ---')
  declare ui_knob AtkFade(MinAtkFade,MaxAtkFade,1)  { Percent }
  { AtkFade sets Seg 1 rise time as percentage of XTime }
    set_text(AtkFade,"Atk Fade")
    set_knob_unit(AtkFade,KNOB_UNIT_PERCENT)
    AtkFade := MainParms[uAtkFade]
  declare ui_knob RlsFade (0,MaxRlsFade,1) { Percent }
  { Release Fade-out time as percent of XTime }
    set_text(RlsFade,"Rls Fade")
    set_knob_unit(RlsFade,KNOB_UNIT_PERCENT)

    RlsFade := MainParms[uRlsFade]
  declare ui_menu RlsMode     { Release Mode Menu }

    add_menu_item (RlsMode,'Knob Setting        Fade-out ends at RlsFade*XTime',RM_Knob)
    add_menu_item (RlsMode,'  Key-Lift            Fade-out ends at key release',RM_KeyUp)  

    RlsMode := MainParms[uRlsMode]   { Inz to Inst_0 as default }
    _read_persistent_var(RlsMode) { Recover last release mode } 

    _read_persistent_var(RlsFade) { Recover last knob setting } 

    Set_RlsKnob                   { Re-establish RlsFade knob conditions }

  { ------------ Main Control Panel, Column 3 --------------- }
  declare ui_knob NodeVol(MinNodeVol,MaxNodeVol,1)  { Percent }
  { Max Volume of seg 1 as percent of Vmax }
    set_text(NodeVol,"Node Vol")
    set_knob_unit(NodeVol,KNOB_UNIT_PERCENT)

    NodeVol := MainParms[uNodeVol]
  declare ui_knob Offset (0,MaxOfst,1)
    set_knob_unit(Offset,KNOB_UNIT_MS)
    set_text(Offset,' Offset')

    Offset := MainParms[uOffset]     { Sample-start offset in ms }
  declare ui_menu OfstMode    { Mode selector for sample-start Offset }

    add_menu_item (OfstMode,' Auto Mode 1        From start of prior note',Ofst_Auto1)
    add_menu_item (OfstMode,' Auto Mode 2       From start of current Phrase',Ofst_Auto2)
    add_menu_item (OfstMode,' Manual Mode       Time setting of Offset Knob',Ofst_Man)
    add_menu_item (OfstMode,' Manual +Rand      Offset Knob + random add-on',Ofst_Rand)
    OfstMode := MainParms[uOfstMode] { Inz to Inst_0 as default }
    _read_persistent_var(OfstMode)   { Recover last offset mode } 

    _read_persistent_var(Offset)     { Recover last knob setting } 

    Set_OfstKnob                     { Re-establish Offset knob conditions }  
      
  { ------------ Main Control Panel, Column 4 --------------- }
  declare ui_label BTimeCap (1,1)   { BTime Hdr }

    set_text(BTimeCap,'    Bend Time')
  declare ui_label BendCap2 (1,1)

    set_text(BendCap2,'For Minor 2nd  >>')
  on_init_BTime { Declare BTime Knob and associated MIDI controls, *SLS }    
    
  { ------------ Main Control Panel, Column 5 --------------- }
  declare ui_label BendCap1 (1,1)   { Bend/Slope Hdr }

    set_text(BendCap1,' Amount & Slope')
  declare ui_knob Slope (MinSlope,MaxSlope,10) { Ocatave Slope scaled by 10 }
    set_text(Slope,' Slope')

    Slope := MainParms[uSlope]
  on_init_Bend { Declare Bend Edit Box and associated MIDI controls, *SLS* } 
      
  { ------------ Main Control Panel, Column 6 --------------- }
  declare @LowKey      { String version of Global[G_Low_Key] }

  declare @TopKey      { String version of Global[G_Top_Key] } 
  declare ui_label IR_Box (1,2)

    ShowIR  { Display default or last saved IR } 
  declare ui_label ModeCap (1,1)
    set_text(ModeCap,' - Mode Menu -')
  declare ui_button SetIR      { Button to initiate instrument }

    set_text($SetIR,SetCap)    {  range setting mode }
    SetIR := false
  on_init_SLSMode       { Build the SLSMode Menu }
  on_init_LegCC         { Build the SLSMode CC Menu }
    
  on_init_CCMenus       { Build the CC assignment menus }
  on_init_Std_PanelMap  { Build the StdPanel_Map, *SLS* }  
  { ------------------- RenPanel & ErrPanel --------------------- }
  on_init_RenPanel     { Declare and initialize Rename-Panel, *UPS* }
  on_init_ErrPanel     { Declare and initialize Error-Panel, *UPS*  }
  
  { ------------------- Note and Xfade Variables ------------------ }
  family notes  { New/Old note parameters }
    declare note[2]       { MIDI note number of played note }
    declare velocity[2]   { Velocity of played note }
    declare parent[2]     { ID of played note }
    declare child[2]      { ID Mark mask of generated child note/s }
    declare pedal[2]      { Sustain pedal state for note, true = down }
    declare 0tune[2]      { Pitch drop to restore original tuning }
    declare 0db[2]        { Volume drop to restore initial volume }
    declare new           { Index of current note (toggles between 0 and 1) } 
    declare old           { Index of prior note (toggles between 1 and 0) }
  end family { notes }
  
  notes.new := 0          { Initialize to 'idle', ie no phrase active }
  notes.old := 1
  notes.child[notes.new] := NoID
  
  family fade_time   { XFade times in micro-seconds }
    declare full     { XFade knob setting in usec }
    declare atk1     { Time for attack segment 1 of fade-in }
    declare atk2     { Time for attack segment 2 of fade-in }
  end family { fade_time }
  
  family fade_ticks  { XFade times in clock Ticks (100usec each) }

    declare rls1     { Fade-out of release segment 1 in Ticks }

    declare atk1     { Time for attack segment 1 in Ticks }

    declare atk2     { Time for attack segment 2 in Ticks }

  end family { fade_ticks }
  
  declare bend_amount { Total Bend Amount in mcts }    

  declare bend_step   { Bend step in ucts per tick }    

  declare bend_ticks  { Remaining Ticks in BTime interval }
  declare fade_in_ticks   { Remaining fade-in Ticks for latest note }      

  declare fade_out_ticks  { Remaining fade-out Ticks for prior note }
  
  { --------------------- Interscript Support Data ------------------- }
  declare markx       { Current note set id mark index }
  declare num_notes   { nlayers from VXF } 
  declare NoHold := true  { VXF present and active }
  declare polyphonic EVP4 { Pseudo, 'local' event parm }   
 
  { -------------------- General Variables ------------------- }
  declare CC_Pending  { 0 = Idle, 1 = waiting for CC selection, }
                      { -1 = Cancellation request by another BtnMenu }
                      { -2 = 'Outside' cancellation request }
  declare Command := No_Cmd   { Preset Menu Active Command }
  declare Get_Low_Key { Instrument Range Keyboard Entry Flag }
  declare LastCC      { Last CC moved, used by Assign_ACC & Assign_CC }
  declare PedalDown   { Flag denoting sustain pedal is depressed }
  declare Ref_Time    { Start time of phrase or prior note-on in msecs }
  declare XFActv      { Flag set while crossfade is active }
  
  { --------- Save/Restore the following with preset/patch ---------- }
  make_persistent(Preset)     { Column 1 }
  make_persistent(XTime.Knob)
  make_persistent(XTime.iKnob)
  make_persistent(XTime.Range)  

  make_persistent(XTime.CC)  
  
  make_persistent(AtkFade)    { Column 2 }
  make_persistent(RlsFade)
  make_persistent(RlsMode)
  
  make_persistent(NodeVol)    { Column 3 }
  make_persistent(Offset)

  make_persistent(OfstMode)
    
  make_persistent(BTime.Knob) { Column 4 }
  make_persistent(BTime.iKnob)
  make_persistent(BTime.Range)  

  make_persistent(BTime.CC)
  
  make_persistent(Slope)      { Column 5 }
  make_persistent(Bend.Knob)
  make_persistent(Bend.iKnob)
  make_persistent(Bend.Range)

  make_persistent(Bend.CC)
  
  make_persistent(SLSMode)    { Column 6 } 
  make_persistent(Leg.CC)
  
  make_persistent(Global)     { Inter-Script Data Array }
  make_persistent(UserParms)  { User Presets } 
  make_persistent(UserNames)  { User Preset Names, numeric }
  
  _read_persistent_var(UserParms)   { Recover Prior Presets, V110 and up }
  _read_persistent_var(PresetParms) { Recover Prior Presets, V105 }
  Update_Format    { If version upgrade, re-format presets, *UPS* }
  message('')      { Clear K2's status area }

end on { init }


on note
  ISCS_NCB(MsgHandler,OmniHandler) 
  if SLSMode = SIPS_Off    { Determine how to handle Pedaled notes }  

    TestEcho(VXF_ID)       { Set NoHold flag if VXF is present }

    NoHold := ReplyCode    { If VXF is online, let it handle Hold }
    EVP4 := Thru_Note      { Tag to identify later in RCB }
    exit                   { Leave note unprocessed with no EP[3] tag }

  end if   
  if SetIR = true          { User is setting a new Instrument }

    ignore_event(EVENT_ID) {  Range so Don't sound this note  }

    SetInstRange(EVENT_NOTE)  { Call Instrument Range Routine }
    exit

  end if { Set Instrument Range }
  if in_range(EVENT_NOTE,Global[G_Low_Key],Global[G_Top_Key])

    ProcessNote  { Process this note with legato simulation logic }

  end if
end on { note }


on release
  ISCS_RCB(CallHandler)  
  if SLSMode # SIPS_Off  { SLS is enabled }
    Do_SLS  { Process Legato notes & carryover }
  else  { SLS is disabled }
    Do_Thru { Process pass-thru notes & carryover }
  end if   

end on { release }

on controller
  if CC_NUM = 0

    exit    { CC0, MIDI Bank Select, Never Used by SIPS }

  end if

  LastCC := CC_NUM    { Remember for CC Assign functions }

  if CC_NUM = Leg.CC  { Check for possible Mode change request }

    select CC[Leg.CC]      { Update Mode }

      case 0              { Set Mode to Disable SLS }
        SLSMode := SIPS_Off
      case 1 to 63        { Set Mode to Solo }

        SLSMode := Solo_Mode

      case 64 to 127      { Set Mode to Legato }

        SLSMode := Legato_Mode

    end select

    New_Mode(SLSMode)     { Take needed action }

    exit

  end if { LegCC }

  TestPedal    { Check sustain pedal for deferred note offs }

  if SLSMode # SIPS_Off

    Update_Panel  { If SLS is active, update panel controls }

  end if

end on { controller }

{ --------------------------- UI Callbacks ------------------------- }
                     { Assign or De-assign MIDI CCs }
on ui_control (Bend.Btn) { Assign Bend CC }

  SetACC(Bend)

end on { Bend.Btn }                     

on ui_control (BTime.Btn) { Assign BTime CC }
  SetACC(BTime)
end on { BTime.Btn }

on ui_control (Leg.Btn) { User is assigning/de-assigning the Leg Mode CC }
  SetCC(Leg)
end on { LegCC }

on ui_control (XTime.Btn) { Assign XTime CC }

  SetACC(XTime)

end on { XTime.Btn }

                     { Change CC add-on Range }
on ui_control (Bend.Range) { User has changed the Bend CC add-on Range }
  Change_Range(Bend)

end on { Bend.Range }



on ui_control (BTime.Range)  { User has changed the BTime CC add-on Range }
  Change_Range(BTime)    

end on { BTime.Range }

on ui_control (XTime.Range)  { User has changed the XTime CC add-on Range }
  Change_Range(XTime)

end on { XTime.Range }

                    { Set Panel Knob or Edit Box Value }
on ui_control (Bend.Knob)  { User has changed the Bend 'Knob' (Edit) value }
  Set_Knob(Bend)

end on { Bend.Knob }

on ui_control (BTime.Knob)    { User has changed the BTime Knob value }
  Set_Knob(BTime)

end on { BTime.Knob } 

on ui_control (XTime.Knob)    { User has changed the XTime Knob value }
  Set_Knob(XTime)

end on { XTime.Knob }
                                               
{ ---------- Preset Menu and Misc UI Control Callbacks ------------- }
on ui_control (Offset)
  if ((OfstMode = Ofst_Man) or (OfstMode = Ofst_Rand))
    set_knob_label(Offset,'')  { Cancel any numeric display override }
  end if
end on { Offset }

on ui_control (OfstMode)
  Set_OfstKnob    { Set Offset Knob data display according to OfstMode }
end on { OfstMode }

on ui_control (Preset)  { Process Preset Menu selection }

  Do_Preset        { Process Menu selection }

end on { Preset Menu }

on ui_control (RlsFade)
  if RlsMode = RM_Knob
    set_knob_label(RlsFade,'') { Cancel any numeric display override }
  end if
end on { RlsFade }

on ui_control (RlsMode)
  Set_RlsKnob      { Set RlsKnob data display according to RlsMode }
end on { RlsMode }

on ui_control (SetIR) 
{ Start or Abort a new Inst Range input sequence }
  if SLSMode = SIPS_Off
    SetIR := 0
    exit                 { Abort if SLS is disabled }
  end if
  if SetIR = true        { Start setup mode for new IR input sequence } 

    Get_Low_Key := true  { Low/High key entry always starts with Low } 

    set_text(SetIR,' Hit Lowest Key') { Prompt User for Low Key } 

  else { User abort of sequence } 

    set_text(SetIR,SetCap)  { Normal 'off button' caption } 

  end if
end on { Set_Range Button }

on ui_control (SLSMode)
  New_Mode(SLSMode)  
end on { SLSMode Menu }

import "SIPSXM-Ren V151.txt"  { UI Callbacks for Rename & BtnMenus }


{ --------------------- SLS-Specific Routines --------------------- }
{ ------------------------ ISCS Handlers ---------------------- }
function CallHandler(selector)

  select selector

    case FetchPreset

      Recall_Preset(Preset)

  end select

end function { CallHandler }


function MsgHandler(sender,msgtype,reply)

  if ((sender = HostSID) and (msgtype = import_block)...
          and (Preset = Import))
    Do_Import  { OK to receive Preset Import }
    reply := mrOK
  end if

end function { MsgHandler }



function OmniHandler(sender,msgtype)

 { Not Used }

end function { OmniHandler }

{ -------------------- End ISCS Handlers ---------------------- }
function Do_Bend   { Bend Old/New note's pitch by tick amount } 

  declare OldTune

  declare NewTune

  

  OldTune := bend_amount - (bend_step*bend_ticks + 500)/1000
  NewTune := -(bend_step*bend_ticks + 500)/1000
  change_tune(notes.child[notes.old],OldTune - notes.0tune[notes.old],1)
  notes.0tune[notes.old] := OldTune

  change_tune(notes.child[notes.new],NewTune - notes.0tune[notes.new],1)
  notes.0tune[notes.new] := NewTune

end function { Do_Bend }


function Do_SLS { Process RCB when SLS is active }

  if EVENT_ID = notes.parent[notes.new]  { Key lift for New note }

    if PedalDown = true  { Sustain Pedal Down }

      notes.pedal[notes.new] := true     { Defer New note-off }

    else                 { Sustain Pedal Up }

      EndPhrase          { End the current phrase }

    end if

    exit

  end if

  { Check for premature key lift of Old note }

  if ((EVENT_ID = notes.parent[notes.old]) and ...   

      (fade_out_ticks > 0) and (RlsMode = RM_KeyUp))

    if PedalDown = true  { Sustain Pedal Down }

      notes.pedal[notes.old] := true    { Defer Old note-off }

    else  { Sustain Pedal Up }

      EndOld             { Retire Old note }

    end if

    exit

  end if

  { If this note is leftover from when the SLS was disabled }

  if ((EVP4 = Thru_Note) and (PedalDown = true) and (NoHold = false)) 

    ignore_event(EVENT_ID)  { Cancel release and mark as held }

    set_event_mark(EVENT_ID,ALL_HELD)

  end if

end function { Do_SLS }



function Do_Thru   { Process RCB when SLS is disabled }

  if EVENT_ID = notes.parent[notes.new] { Leftover legato key lift } 

    if PedalDown = true

      set_event_mark(notes.child[notes.new],ALL_HELD) { Defer end }

    else  { Pedal up }

      note_off(notes.child[notes.new])  { End last note of phrase }

    end if

    notes.child[notes.new] := NoID      { Retire last note id }    

    exit

  end if

  { Process normal thru-notes here if not handled by later script }

  if ((PedalDown = true) and (NoHold = false)) 

    ignore_event(EVENT_ID)              { Defer ending the note }

    set_event_mark(EVENT_ID,ALL_HELD)

  end if  

end function { Do_Thru }


function EndOld   { Stop processing and retire Old note }
  fade_out_ticks := 0          { Discontinue Old note processing }
  note_off(notes.child[notes.old]) { Retire Old note }

  notes.pedal[notes.old] := false  { Reset pedal-deferred flag }
  SendOmniMsg(ending_prior_note)   { Send old note ending msg }
end function { EndOld }

function EndPhrase { Stop processing and retire Old/New notes }
  StopFade         { Abort XFade and retire Old note }
  note_off(notes.child[notes.new]) { Retire New note }

  notes.new := 0   { Reset note indices }

  notes.old := 1

  notes.child[notes.new] := NoID   { End of Phrase }  

  SendOmniMsg(ending_phrase)       { Send last note end msg }

end function { EndPhrase }

{ Set new note parms from played note, then generate child note/s }

function Gen_Notes(ofst)

  declare n

  declare id
  

  notes.note[notes.new] := EVENT_NOTE  { MIDI note# of newest played note }

  notes.velocity[notes.new] := EVENT_VELOCITY

  notes.parent[notes.new] := EVENT_ID  { ID of newest played note }

  notes.pedal[notes.new] := false

  for n := 1 to num_notes    { Generate child note/s }

    id := play_note(EVENT_NOTE,EVENT_VELOCITY,ofst,0)

    set_event_mark(id,Mark[markx])

    set_event_par(id,3,sh_left(markx,11) + SLS_ID) { Add Tag to notes }

  end for   

  notes.child[notes.new] := by_marks(Mark[markx])
  notes.0tune[notes.new] := 0   { Initialize tuning reference }

end function { Gen_Notes }

function Get_Offset(ofst) { Determine sample-start offset of next fade-in note }

  declare const Ofst_Step := 50000 { Random offset add-on step increment, 50ms }

  declare const Ofst_Steps := 8    { Number of randomly-chosen offset steps }  

  declare NewRand      { New random add-on to be used for Manual Offset }

  declare OldRand      { Previously used random add-on for Manual Offset }

  

  ofst := (ENGINE_UPTIME - Ref_Time)*1000   { Time since last ref mark }

  select OfstMode

  case Ofst_Auto1            { Auto1 uses time from start of last note }

    Ref_Time := ENGINE_UPTIME   { Update Ref_Time to start of new note }

  case Ofst_Auto2  

    NOP         { No action needed, use Ref_Time from start of phrase }

  case Ofst_Man

    ofst := Offset*1000    { Use Knob setting for sample-start offset }

  case Ofst_Rand           { Use Knob + random increment }

    NewRand := OldRand

    while NewRand = OldRand            { Get a new random index that }

      NewRand := random(0,Ofst_Steps)  {  is different from the last }

    end while

    ofst := Offset*1000 + NewRand*Ofst_Step     { Compute new offset }

    OldRand := NewRand     { Update OldRand for next time }

  end select

end function { Get_Offset }

function New_Mode(mode) { Mode has changed }
  if mode # LastMode    { Mode has changed }

    LastMode := mode    { Update LastMode }

    if ((mode = SIPS_Off) and (notes.pedal[notes.new] = true))
      { SLS disabled with pedal down }

      notes.pedal[notes.new] := false  { Mark last sustained note }

      set_event_mark(notes.child[notes.new],ALL_HELD)

      notes.child[notes.new] := NoID   { Then retire its id }

    end if 

  end if  

end function { New_Mode }

{ ProcessNote    This routine contains the phrase detection

                 and legato simulation control logic }
function ProcessNote

  declare LegOfst  { Legato sample-start offset in usec }
  

  ignore_event(EVENT_ID) { Discard actual 'played' note }
  SendMsg(VXF_ID,get_nlayers)  { Poll VXF for num_notes }
  select ReplyCode
    case mrNone  { No VXF script detected, }
      num_notes := 1   { Default to standalone SLS mode }

    case mrOK    { Use VXF Mode, get num_notes for next set}

      num_notes := ReplyData[0]

    case vxfFull { VXF polyphony at max, }
      exit       { Discard note }      
  end select

  if notes.child[notes.new] = NoID { Starting a new phrase }
    markx := 0                    { Reset id mark index }
    SendOmniMsg(starting_phrase)  { Broadcast start phrase msg }
    Ref_Time := ENGINE_UPTIME     { Get start time of phrase  }
    LegOfst := 0                  { Reset Auto Offset }

    Gen_Notes(LegOfst)            { Generate child note set }

  else { Continue Phrase }
    markx := markx + 1 .and. 3    { Cycles 0,1,2,3,0,1, ... }

    StopFade      { Terminate any in-process crossfade/bend }
    notes.old := notes.new  { Exchange new/old note indices }
    notes.new := notes.new + 1 .and. 1
    Get_Offset(LegOfst)    { Determine note-start offset to use }

    Gen_Notes(LegOfst)     { Generate child note set } 

    XFade   { Now start Crossfade/Bend of the Old/New notes }

  end if  

end function { ProcessNote }

function Send_IR  { Broadcast Instrument Range to rest of the suite }

  declare IR[3]
  
  IR[0] := Global[G_Low_Key]
  IR[1] := Global[G_Top_Key]
  SendOmniMsgD(new_inst_range,IR)
end function { Send_IR }

{ SetInstRange(key)     Allows user to set Low then High key

                        of instrument range from MIDI keyboard } 

function SetInstRange(key)

  if Get_Low_Key = true 

    Global[G_Low_Key] := key  { Update numeric value of Low_Key } 

    ShowIR                    { Update display of Instrument Range } 

    set_text(SetIR,' Hit Highest Key')  { Prompt user for High Key }

    Get_Low_Key := false      { Assume next note will be the High Key }     

  else  { This note is the High Key }                       

    Global[G_Top_Key] := key  { Update numeric value of Top_Key } 

    ShowIR                    { Update display of Instrument Range } 

    set_text(SetIR,SetCap)    { Normal 'off button' caption } 

    SetIR := false  { We're done, switch back from IR entry to play mode }

  end if

  Send_IR                     { Broadcast new IR to rest of suite }

end function { SetInstRange }


function Set_OfstKnob { Sets Offset knob data display mode }

  select OfstMode

  case Ofst_Auto1 to Ofst_Auto2           { For these modes, darken }

    set_knob_unit(Offset,KNOB_UNIT_NONE)  {  knob's data display    }

    set_knob_label(Offset,' ')

  case Ofst_Man to Ofst_Rand                 { For these modes, use }

    set_knob_unit(Offset,KNOB_UNIT_MS)       {  normal data display }

    set_knob_label(Offset,' ' & Offset)      { Use txt format until }

  end select                                 {  knob is moved       } 

end function { Set_OfstKnob }



function Set_RlsKnob { Sets RlsFade knob data display mode }

  select RlsMode

  case RM_KeyUp  { For this mode, darken knob's data display }

    set_knob_unit(RlsFade,KNOB_UNIT_NONE)

    set_knob_label(RlsFade,' ')

  case RM_Knob   { For this mode, use normal data display }

    set_knob_unit(RlsFade,KNOB_UNIT_PERCENT)

    set_knob_label(RlsFade,'   ' & RlsFade) { Use text format until }

  end select                                {  knob is moved        } 

end function { Set_RlsKnob }

function SetupXFade { Get current panel parameters to control XFade }

  declare Interval  { New/Old note separation in semitones }

  

  fade_time.full := 1000*XTime.Knob  { Compute full Xfade time in micro-seconds }

  fade_time.atk1 := fade_time.full*AtkFade/NodeVol      { Fade-in segment 1 in us }

  fade_time.atk2 := fade_time.full*(100 - AtkFade)/100  { Fade-in segment 2 in us }

  fade_ticks.rls1 := fade_time.full/Tick { Compute full Xfade time in clock Ticks }

  fade_ticks.atk1 := fade_ticks.rls1*AtkFade/100   { Fade-in segment 1 in Ticks }

  if RlsMode = RM_Knob  { Shorten fade-out Ticks by knob setting if selected }

    fade_ticks.rls1 := fade_ticks.rls1*RlsFade/100 

  end if

  if AtkFade = 100  { Determine #of Ticks for fade-in segment 2 if any }

    fade_ticks.atk2 := 0  { No segment 2 for AtkFade = 100% }

  else

    fade_ticks.atk2 := fade_time.atk2/Tick

  end if

  Interval := abs(notes.note[notes.new] - notes.note[notes.old])

  if ((BTime.Knob = 0) or (Interval = 0))

    bend_ticks := 0  { No bend }

  else { Use bending }

    bend_ticks := BTime.Knob*1000/Tick   { Clock Ticks for BTime interval }

    bend_amount := Bend.Knob*1000  { Bend in mcts for one semitone interval }

    if ((Slope # 10) and (Interval # 1)) { Increase bend as required }

      bend_amount := bend_amount + ...
                     ((Slope - 10)*(Interval - 1)*bend_amount + 55)/110

    end if

    if notes.note[notes.new] < notes.note[notes.old]

      bend_amount := -bend_amount  { Bend downward }

    end if

    bend_step := 1000*bend_amount/bend_ticks  { Pitch change per tick in ucts }

  end if

end function { SetupXFade }



function StopFade { Terminate any in-process crossfade/bend }

  declare Busy

  

  while Busy = true

    wait(Tick)    { No re-entry allowed }

  end while

  if XFActv = true

    if fade_out_ticks > 0  { Old note not yet retired }

      EndOld               { Retire it now }

    end if

    fade_in_ticks := 0     { End processing of New note }

    bend_ticks := 0        { End processing of bends }

    Busy := true  { Lockout any other callers until done }

    while XFActv = true

      wait(Tick)  { Wait until processing actually ends }

    end while

    Busy := false { Allow waiting callers at next task switch }    

  end if

end function { StopFade }

{ TestPedal     Set/reset sustain PedalDown flag and if

                false, act on any deferred note offs }
function TestPedal
  if CC_NUM = Pedal         { Sustain pedal has changed }
    if CC[Pedal] < 64       { Check and set current pedal state }
      PedalDown := false
      if SLSMode # SIPS_Off { Handle pedal up when SLS is active }

        if ((notes.pedal[notes.old] = true) and (fade_out_ticks > 0))

          EndOld  { Old note-off was deferred, end it now }

        end if

        if notes.pedal[notes.new] = true  { New note-off was deferred }

          notes.pedal[notes.new] := false

          EndPhrase        { End the phrase now }

        end if

      end if

      note_off(by_marks(ALL_HELD)) { End any bypassed, held notes }

    else { CC[Pedal] > 63 }
      PedalDown := true
    end if

  end if { CC_NUM = Pedal } 

end function { TestPedal }

{ XFade     Crossfade and bend OldGen Note with NewGen Note
            per current panel settings. In Solo Mode, simply
            retire OldGen Note }
function XFade

  if ((SLSMode = Solo_Mode) or (XTime.Knob = 0))
    EndOld                 { Retire OldGen Note, no crossfade or bend }

  else { Legato Mode }

    SetupXFade  { Get current panel parameters to control XFade }

    fade_out(notes.child[notes.old],fade_time.full,0) { Start Old Note fade-out }

    fade_out_ticks := fade_ticks.rls1  { Ticks to reach Release Fade time }

    fade_in(notes.child[notes.new],fade_time.atk1) { Start New note fade-in seg-1 }
    fade_in_ticks := fade_ticks.atk1   { Ticks to reach seg-1/seg-2 fade-in node }
    XFActv := true              { Set this flag while XFade is in process }

    SendOmniMsg(starting_xfade) { Disable Vibrato during XFade } 

    while ((fade_out_ticks > 0) or (fade_in_ticks > 0) or (bend_ticks > 0))

      dec(fade_out_ticks)  { Reduce remaining ticks for each of the 3 activities }

      dec(fade_in_ticks)

      dec(bend_ticks)

      if fade_out_ticks = 0  { Fade-out has just completed }

        EndOld               { Retire Old note }

      end if

      if ((fade_in_ticks = 0) and (fade_ticks.atk2 > 0)) { Fade-in seg-1 done }

        fade_in(notes.child[notes.new],fade_time.atk2)   { Start fade-in Seg-2 }

        fade_in_ticks := fade_ticks.atk2   { Start segment 2 counter }

        fade_ticks.atk2 := 0   { No more segments }

      end if

      if bend_ticks >= 0

        Do_Bend     { Keep calling this function until BTime expires }

      end if

      wait(Tick)    { Pause for update clock interval }

    end while       { Keep going until all 3 activities are completed }
    SendOmniMsg(ending_xfade)  { Re-enable Vibrato after XFade }

    XFActv := false { Reset this flag when XFade is fully completed or cancelled }

  end if

end function { XFade }

